﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Runtime.InteropServices;
using System.Reflection;

using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swcommands;
using SolidWorks.Interop.swconst;
using SolidWorks.Interop.swpublished;

using SwAddinHelpers.Contracts;
using SwAddinHelpers.Attributes;
using SwAddinHelpers;
using SwAddinHelpers.TaskPane;
using SwAddinHelpers.PropertyManager;
using SwAddinHelpers.FeatureView;
using SwAddinHelpers.ModelView;
using SwAddinHelpers.Sinks;

namespace SwAddinHelpers.Base
{
    
    // The level at which SinkMethodAttribute is registered or unregistered.
    public enum enumEventSubscriptionSource
    {
        GUI_CONTROL,
        GUI_HOST
    }

    public class SubscribedEventInfo
    {
        public SubscribedEventInfo(Delegate EventDelegate, enumEventSubscriptionSource EventSource, enumEventSink EventSink)
        {
            eventDelegate = EventDelegate;
            eventSource = EventSource;
            eventSink = EventSink;
        }

        public Delegate eventDelegate = null;
        public enumEventSubscriptionSource eventSource = enumEventSubscriptionSource.GUI_CONTROL;
        public enumEventSink eventSink;

    }

    public abstract class UserControlHostBase: AddinProxy
    { 

        #region PMP Host use only
        protected int _HostedControlId = -1;

        public int HostedControlId
        {
            get { return _HostedControlId; }
        }

        protected int GetControlId()
        {
            try
            {
                return (int)_externalControl.GetType().GetField("CONTROLID", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null);
            }
            catch { return -1; }
        }

        protected short[] GetPreDefinedControlSize()
        {
            // this method is used for creating PropertyManagerPages ONLY.
            
            short[] size = new short[] { 100, 100 };

            try
            {
                size[0] = (short)_externalControl.GetType().GetField("WIDTH", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null);
                size[1] = (short)_externalControl.GetType().GetField("HEIGHT", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null);
            }
            catch { }

            return size;
        }
        #endregion

        #region Invokable Method Functionality
        public List<string> GetInvokableMethodNames()
        {
            List<string> invokables = new List<string>();
            
            try
            {
                MethodInfo[] mis = _GuiControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                foreach (MethodInfo mi in mis)
                {
                    InvokableMethodAttribute[] ima = (InvokableMethodAttribute[])mi.GetCustomAttributes(typeof(InvokableMethodAttribute), false);

                    if (ima.Length > 0)
                    {
                        foreach (InvokableMethodAttribute im in ima)
                        {
                            invokables.Add(im.FriendlyName);
                        }
                    }
                }
            }
            catch { }

            return invokables;
        }

        public List<MethodInfo> GetInvokableMethods()
        {
            List<MethodInfo> invokables = new List<MethodInfo>();

            try
            {
                MethodInfo[] mis = _GuiControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                foreach (MethodInfo mi in mis)
                {
                    InvokableMethodAttribute[] ima = (InvokableMethodAttribute[])mi.GetCustomAttributes(typeof(InvokableMethodAttribute), false);

                    if (ima.Length > 0)
                    {
                        foreach (InvokableMethodAttribute im in ima)
                        {
                            invokables.Add(mi);
                        }
                    }
                }
            }
            catch { }

            return invokables;
        }

        public bool InvokeMethod(string InvokableMethodName, params object[] parameters)
        {
            MethodInfo targetMethod = null;

            try
            {
                MethodInfo[] mis = _GuiControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                bool breakNow = false;

                foreach (MethodInfo mi in mis)
                {
                    InvokableMethodAttribute[] ima = (InvokableMethodAttribute[])mi.GetCustomAttributes(typeof(InvokableMethodAttribute), false);

                    if (ima.Length > 0)
                    {
                        foreach (InvokableMethodAttribute im in ima)
                        {
                            if (im.FriendlyName == InvokableMethodName)
                            {
                                targetMethod = mi;
                                breakNow = true;
                                break;
                            }
                        }
                    }

                    if (breakNow) break;
                }


            }
            catch { }

            if (targetMethod == null)
                return false;

            try
            {
                targetMethod.Invoke(_GuiControl, parameters);
                return true;
            }
            catch { return false; }
        }
        #endregion

        #region External and Gui Control
        protected UserControlBase _GuiControl;

        // guicontrol represents the control that is created by solidworks; in essence externalControl and GuiControl are the same type
        public UserControlBase GuiControl
        {
            get { return _GuiControl; }
            set 
            { 
                _GuiControl = value;

                if (IsLateSubscriber)
                {
                    IsLateSubscriber = false;
                    AllowDuplicateSubscription= true;

                    foreach (string s in SubscribedEvents.Keys)
                    {
                        SubscribeEvent(SubscribedEvents[s].eventSink, SubscribedEvents[s].eventSource);
                    }

                    AllowDuplicateSubscription = false;
                }
            }
        }

        private dynamic _externalControl = null;

        // externalConrol represents the UserControl object of the external binary
        public dynamic externalControl
        {
            get { return _externalControl; }
            set
            {
                _externalControl = value;
                HostedControlTypeProgId = GetControlProgId();
                _externalControl.IsCreated = true;
            }
        }

        public string HostedControlTypeProgId = "";

        private string GetControlProgId()
        {
            try
            {
                return (string)_externalControl.GetType().GetField("PROGID", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static).GetValue(null);
            }
            catch { return ""; }
        }
        #endregion

        #region Subscription Services
        public List<SinkMethodAttribute> GetSinkableMethodAttributes(dynamic gui)
        {
            List<SinkMethodAttribute> sinkables = new List<SinkMethodAttribute>();

            try
            {
                MethodInfo[] mis = gui.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

                foreach (MethodInfo mi in mis)
                {
                    SinkMethodAttribute[] ima = (SinkMethodAttribute[])mi.GetCustomAttributes(typeof(SinkMethodAttribute), false);

                    if (ima.Length > 0)
                    {
                        foreach (SinkMethodAttribute im in ima)
                        {
                            im.methodInfo = mi;
                            sinkables.Add(im);
                        }
                    }
                }
            }
            catch { }

            return sinkables;
        }
        
        // Dictionary to hold current host's and gui control's subscriptions
        public Dictionary<string,SubscribedEventInfo> SubscribedEvents = new Dictionary<string,SubscribedEventInfo>();

        // Retrieve the method to be invoked when the specified event fires
        private MethodInfo GetTargetMethod(enumEventSink SourceEvent, enumEventSubscriptionSource SourceControl)
        {
            try
            {
                MethodInfo targetMethod = null;
                MethodInfo[] mis = null;

                if (SourceControl == enumEventSubscriptionSource.GUI_CONTROL)
                {
                    mis = _GuiControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                }
                else if (SourceControl == enumEventSubscriptionSource.GUI_HOST)
                {
                    mis = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                }

                #region old code
                /*else
                {
                    MethodInfo[] mis1 = _GuiControl.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                    MethodInfo[] mis2 = this.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                    mis =  mis1.Concat(mis2).ToArray();
                }*/
                #endregion

                bool breakNow = false;

                foreach (MethodInfo mi in mis)
                {
                    SinkMethodAttribute[] sma = (SinkMethodAttribute[])mi.GetCustomAttributes(typeof(SinkMethodAttribute), false);

                    if (sma.Length > 0)
                    {
                        foreach (SinkMethodAttribute sm in sma)
                        {
                            if (sm.SourceEvent == SourceEvent)
                            {
                                // target method found and can now be returned
                                targetMethod = mi;
                                breakNow = true;
                                break;
                            }
                        }
                    }

                    if (breakNow) break;
                }

                return targetMethod;
            }
            catch { return null; }
        }

        protected bool IsLateSubscriber = false;
        private bool AllowDuplicateSubscription = false;
        public bool IsControlCreated = false;

        // Called during host creation
        // Registers the GUI control's events after the host is created if the control requested it
        public void SubscribeGuiControlEvents()
        {
            List<SinkMethodAttribute> attribs = GetSinkableMethodAttributes(externalControl);
            
            foreach (SinkMethodAttribute attrib in attribs)
            {
                if (attrib.TieOnHostCreation) SubscribeEvent(attrib.SourceEvent, enumEventSubscriptionSource.GUI_CONTROL);
            }
        }

        // Builds the key used in the subscription dictionary
        private string GetSubscriptionKey(enumEventSink SourceEvent, enumEventSubscriptionSource SubscriptionSource)
        {
            return SourceEvent.ToString() + SubscriptionSource.ToString();
        }

        // Subscribes a method to an event
        public void SubscribeEvent(enumEventSink SourceEvent, enumEventSubscriptionSource SubscriptionSource)
        {
            //http://msdn.microsoft.com/en-us/magazine/dd347981.aspx
            //http://msdn.microsoft.com/en-us/library/system.reflection.eventinfo.addeventhandler.aspx

            Delegate myDelegate = null;
            bool AddedToCollection = false;

            string key = GetSubscriptionKey(SourceEvent, SubscriptionSource);

            if (!SubscribedEvents.Keys.Contains(key) || AllowDuplicateSubscription)
            {
                if (IsLateSubscriber)
                {
                    SubscribedEvents.Add(key, new SubscribedEventInfo(null, SubscriptionSource, SourceEvent));
                    return;
                }

                MethodInfo targetMethod = GetTargetMethod(SourceEvent, SubscriptionSource);

                if (targetMethod != null)
                {
                    try
                    {
                        EventInfo ei = _OwnerAddin.MainAppEventSink.GetType().GetEvent(SourceEvent.ToString());

                        if (SubscriptionSource == enumEventSubscriptionSource.GUI_CONTROL)
                            myDelegate = Delegate.CreateDelegate(
                                        ei.EventHandlerType,
                                        _GuiControl,
                                        targetMethod);
                        else if (SubscriptionSource == enumEventSubscriptionSource.GUI_HOST)
                            myDelegate = Delegate.CreateDelegate(
                                        ei.EventHandlerType,
                                        this,
                                        targetMethod);

                        ei.AddEventHandler(_OwnerAddin.MainAppEventSink, myDelegate);

                        #region old code
                        /*else
                        {
                            try
                            {
                                myDelegate = Delegate.CreateDelegate(
                                        ei.EventHandlerType,
                                        _GuiControl,
                                        targetMethod);

                                ei.AddEventHandler(_OwnerAddin.MainAppEventSink, myDelegate);
                            }
                            catch { }
                            try
                            {
                                myDelegate = Delegate.CreateDelegate(
                                          ei.EventHandlerType,
                                          this,
                                          targetMethod);

                                ei.AddEventHandler(_OwnerAddin.MainAppEventSink, myDelegate);
                            }
                            catch { }

                        }*/
                        #endregion

                        if (!SubscribedEvents.Keys.Contains(key))
                        {
                            SubscribedEvents.Add(key, new SubscribedEventInfo(myDelegate, SubscriptionSource, SourceEvent));
                            AddedToCollection = true;
                        }
                        else
                        {
                            SubscribedEvents[key].eventDelegate = myDelegate;
                        }
                    }
                    catch (ArgumentException aex)
                    {
                        MessageBox.Show("Event Subscription Failed: " +
                            "\r\n\r\nSource Event: " + SourceEvent +
                            "\r\nSubscription Source: " + SubscriptionSource +
                            "\r\nProgId(if available): " + HostedControlTypeProgId +
                            "\r\n\r\nCheck that your target method matches that of the delegate.", aex.Message);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Event Subscription Failed: " +
                            "\r\n\r\n" + ex.Message +
                            "\r\n\r\n" + ex.StackTrace);
                    }
                    finally
                    {
                        _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Subscription,
                            HostedControlTypeProgId,
                                new string[] { "sevt: " + SourceEvent.ToString(), 
                                    "progid: " + HostedControlTypeProgId,
                                    "source: " + SubscriptionSource.ToString(),
                                    "addedToColl: " + AddedToCollection.ToString() });
                    }
                }
                else
                {
                    _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Subscription,
                            "Target Method Not Found!",
                                new string[] { "sevt: " + SourceEvent.ToString(), 
                                    "progid: " + HostedControlTypeProgId,
                                    "source: " + SubscriptionSource.ToString(),
                                    "addedToColl: " + AddedToCollection.ToString() });
                }
            }
        }

        // Unsubscribes a method from an event
        public void UnsubscribeEvent(enumEventSink SourceEvent, enumEventSubscriptionSource SubscriptionSource)
        {
            string key = GetSubscriptionKey(SourceEvent, SubscriptionSource);

            if (!SubscribedEvents.ContainsKey(key))
            {
                _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Unsubscription,
                    "Does not exist in collection!",
                                  new string[] { "sevt: " + SourceEvent.ToString(), 
                                    "progid: " + HostedControlTypeProgId});
                return;
            }

            MethodInfo targetMethod = GetTargetMethod(SubscribedEvents[key].eventSink, SubscribedEvents[key].eventSource);

            bool wasRemoved = false;

            if (targetMethod != null)
            {
                try
                {
                    EventInfo ei = _OwnerAddin.MainAppEventSink.GetType().GetEvent(SubscribedEvents[key].eventSink.ToString());

                    ei.RemoveEventHandler(_OwnerAddin.MainAppEventSink, SubscribedEvents[key].eventDelegate);
                    
                    #region old code
                    /*else
                    {
                        try
                        {
                            ei.RemoveEventHandler(_OwnerAddin.MainAppEventSink, SubscribedEvents[key].myDelegate);
                            
                        }
                        catch { }
                        try
                        {
                            ei.RemoveEventHandler(_OwnerAddin.MainAppEventSink, SubscribedEvents[key].myDelegate);
                           
                        }
                        catch { }

                    }*/
                    #endregion

                    SubscribedEvents[key].eventDelegate = null;
                    SubscribedEvents.Remove(key);
                    wasRemoved = true;
                }
                catch { }
                finally
                {
                    _OwnerAddin.Debug.Manager.PostEvent(AddinManager.enumDebugMsgType.Unsubscription,
                        HostedControlTypeProgId,
                                new string[] { "sevt: " + SourceEvent.ToString(), 
                                    "progid: " + HostedControlTypeProgId,
                                "wasRemoved: " + wasRemoved.ToString() });
                }
            }
        }

        // Called by the user host base during teardown.  Unsubscribes everything.
        private void UnsubscribeAllEvents()
        {
            for (int i = SubscribedEvents.Count - 1; i >= 0; i--)
            {
                UnsubscribeEvent(SubscribedEvents.ElementAt(i).Value.eventSink, SubscribedEvents.ElementAt(i).Value.eventSource);
            }
        }

        // called bye the user host base during teardown.
        public virtual void Teardown()
        {
            UnsubscribeAllEvents();
        }
        #endregion
    }
}
